<?php

//06 ma 2007 - Ruben
//implements Traversable --> geeft error: previously implemented
abstract class abstractModel implements Iterator, ArrayAccess, Countable
{
	const INSERT			= 0;
	const UPDATE			= 1;
	const DELETE			= 2;
	
	protected $aCache		= Array();		//array met alle eerder opgehaalde resultaten
	protected $iCache		= null;			//aantal resultaten in de cache-array
	protected $iTotal		= null;			//totaal aantal resultaten (hoeven nog niet opgehaald te zijn)
	protected $iState;						//status. 3 waardes mogelijk: self::INSERT, self::UPDATE, self::DELETE
	
	protected $iId;							//identifier, bijv regelnummer of record-id
	protected $sSource		= '';			//naam van de source, bijv het bestand of databasetabel
	protected $sPrefix		= '';			//prefix van bijv de identifier, wordt gebruikt voor databasemodels
	protected $aBelongs_to	= Array();
	protected $aHas_many	= Array();		//array met links naar 1:veel relaties (Array('pk' => 'table2.fk'))
	
	protected $bValid		= false;		//wordt gebruikt om te kijken of er nog resultaten zijn
	public $oBench;							//benchmark-object
	
	
	public function __construct ($sSource = '')
	{
		$this->oBench	= new benchmark();
		if (!empty($sSource))
		{
			$this->sSource	= trim($sSource);
		}
		
		$this->oBench->add_mark('constructor '.$sSource);
	}
	
	
	public function dump ($bGet_output = true)
	{
		$this->oBench->start_mark();
		
		if (is_null($this->iTotal))
		{
			$this->load();
		}
		
		if ($this->iCache < $this->iTotal)
		{
			//we zorgen dat alle items zijn doorlopen
			foreach ($this as $aResult);
		}
		$this->oBench->add_mark('created total cache');
		return ($bGet_output) ? sprint_r($this->aCache) : $this->aCache;
	}
	
	//
	//onderstaande 5 methoden komen van de interface 'Iterator'
	//
	/**
	 * This method is called when you'll try to walk trough the object with for example an foreach loop
	 * It checks if there are elements in the array AFTER the current pointer
	 * @return		Void
	 */
	public function next ()			{ $this->bValid = (false !== next($this->aCache)); }
	/**
	 * Deze methode wordt aangeroepen wanneer er wordt gedaan: rewind(#instantie_class), en is bedoeld om de 
	 * interne pointer van de array die doorlopen wordt, weer aan het begin te zetten.
	 * Om te kijken of dat gelukt wordt $this->valid() aangeroepen.
	 * @return		Void
	 */
	public function rewind ()
	{
		if (is_null($this->iTotal))
		{
			$this->load();
		}
		
		$this->bValid = (false !== reset($this->aCache) || $this->iCache < $this->iTotal);
	}
	/**
	 * Deze methode geeft de key terug van het huidige element uit de array en wordt aangeroepen wanneer er 'key(#instantie_class)'
	 * wordt gedaan.
	 * @return		mixed		key van het huidige item uit de array
	 */
	public function key ()			{ return key($this->aCache); }
	/**
	 * Deze methode wordt aangeroepen om te kijken of er nog elementen in de array zitten na de huidige pointer.
	 * @return		boolean		true als er nog elementen zijn, false als die er niet zijn
	 */
	public function valid ()		{ return $this->bValid; }
	/**
	 * Deze methode wordt aangeroepen wanneer er wordt gedaan: current(#instantie_class), en is bedoeld om het 
	 * huidige item uit de array die doorlopen wordt, terug te geven.
	 * @return		mixed		huidige item uit de array
	 */
	public function current ()
	{
		if (!current($this->aCache))
		{
			$this->next();
		}
		return current($this->aCache);
	}
	
	
	//
	//Onderstaande methode komen van de interface 'Countable'
	//
	public function count ()
	{
		return $this->iTotal;
	}
	
	
	//
	//Onderstaande 4 methoden komen van de interface 'ArrayAccess'
	//
	/**
	 * Deze methode zorgt ervoor dat een record wordt toegevoegd/aangepast met de opgegeven waardes uit de array
	 * Voor die array geldt: Array('kolomnaam' => 'waarde', 'kolom2' => 'waarde2');
	 *
	 * Wanneer deze functie voor de eerste keer wordt aangeroepen, en de key '0' is, zal de variabele $this->iState veranderen
	 * naar self::INSERT. Als de key de eerste keer > '0' is, zal de variabele veranderen naar self::UPDATE.
	 * De status zal bij de volgende functie aanroepen niet meer veranderen.
	 * 
	 * De gewijzigde waarde wordt pas opgeslagen als de methode $this->save(); wordt aangeroepen
	 *
	 * Wanneer er dus gedaan wordt:
	 * $this[1] = Array('name' => 'Ruben');
	 * $this->save();
	 * Zal deze methode dus van een record met id = '1' proberen de kolom 'name' te updaten naar 'Ruben'
	 *
	 * @param	mixed		$mKey			ID van record dat gewijzigd/toegevoegd moet worden
	 * @param	mixed		$aValue			Array met waardes. Array('kolomnaam' => 'waarde');
	 * @return	void
	 */
	public function offsetSet ($mKey, $aValue)
	{
		if (is_null($mKey))
		{
			$mKey = count($this->aCache);
		}
		
		//allow the clean-up method of the model to clean all input to prevend any unwanted situations (bad quotes to mix up your queries for example)
		$mKey					= $this->clean_up($mKey);
		$aValue					= $this->clean_up($aValue);
		
		//waarde aan de cache-array toevoegen
		$this->aCache[$mKey]	= $aValue;
		$this->iCache			= count($this->aCache);
		$this->iTotal++;
		
		//we passen alleen de status aan als deze NULL is (dus nog niet is gezet)
		if (is_null($this->iState))
		{
			//status veranderen. Als er een id is opgegeven UPDATE, anders INSERT
			$this->iState = (!is_numeric($mKey) || $mKey > 0) ? self::UPDATE : self::INSERT;
		}
	}
	/**
	 * Deze methode vraagt de waarde op van het record met de id '$mKey'. Als de record niet in de cache-array zit, zal geprobeerd
	 * worden deze op te halen uit de database.
	 * Wanneer er dus wordt gedaan:
	 * echo $this[1];
	 * Zal deze methode dus een record met id = '1' teruggeven
	 *
	 * @param	mixed		$mKey			ID van record dat teruggeven moet worden
	 * @return	array						Als het record bestaat een array met de waardes, anders false
	 */
	public function offsetGet ($mKey)
	{
		$mKey = $this->clean_up($mKey);
		
		if (!isset($this->aCache[$mKey]) && is_null($this->iTotal))
		{
			$this->iId = $mKey;
			$this->load();
			$this->next();
		}
		elseif ($this->iCache < $this->iTotal)
		{
			$this->dump();
			reset($this->aCache);
		}
		return (isset($this->aCache[$mKey])) ? $this->aCache[$mKey] : false;
	}
	/**
	 * Deze methode probeert een record met id '$mKey' te verwijderen. Het verwijderen wordt pas uitgevoerd als hierna de methode
	 * $this->save(); wordt uitgevoerd.
	 * 
	 * WERKT NATIVE ALLEEN MET NUMERIEKE WAARDES!! geen strings!! Gebruik hiervoor $obj->del("id"); ipv unset($obj["id"]);
	 *
	 * Wanneer er dus wordt gedaan:
	 * unset($this[1]);
	 * $this->save();
	 * Zal het record met id = '1' worden verwijderd
	 *
	 * @param	mixed		$mKey			ID van record dat teruggeven moet worden
	 * @return	boolean						Als het verwijderen is gelukt 'true', anders 'false'
	 */
	public function offsetUnset ($mKey)
	{
		$this->del($mKey);
	}
	public function offsetExists ($mKey)
	{
		return isset($this->aCache[$mKey]);
	}
	
	
	/**
	 * Deze methode zorgt ervoor dat wijzigingen aan de cache-array definitief worden opgeslagen. Verwijderde records zullen dus worden
	 * verwijderd, aangepaste records zullen worden aangepast en nieuwe elementen zullen worden toegevoegd.
	 *
	 * Er kan maar 1 handeling per methode-aanroep worden uitgevoerd.
	 *
	 * @return	boolean						true als de mutaties zijn gelukt, anders false
	 */
	public function save ()
	{
		$this->oBench->start_mark();
		switch ($this->iState)
		{
			case self::INSERT:
				return $this->insert();
				break;
			
			case self::UPDATE:
				return $this->update();
				break;
			
			case self::DELETE:
				return $this->delete();
				break;
			
			default:
				return false;
				break;
		}
		$this->oBench('done saving');
	}
	
	
	public function clean_up ($mValue)
	{
		return $mValue;
	}
	
	
	abstract public function load ($iStart = 0, $iRows = 0);
	abstract protected function update ();
	abstract protected function insert ();
	abstract protected function delete ();
	abstract public function add_cond ($sCond);
	abstract public function sort ($sKey = '');
	abstract public function asort ($sKey = '');
	
	
	public function trashcan ()
	{
		$this->aCache	= Array();
		$this->iCache	= null;
		$this->iTotal	= null;
		$this->iState	= null;
		$this->iId		= null;
	}
	
	
	public function del ($mKey)
	{
		if ($this->iState != self::DELETE)
		{
			$this->aCache = Array();
		}
		$this->aCache[$mKey]		= Array();
		$this->iState				= self::DELETE;
	}
}

?>